package org.tamal.jsf.bean;

import java.nio.charset.Charset;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;

import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

import org.tamal.Constants;
import org.tamal.SessionKey;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.utils.SystemProperty;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;

/**
 * This bean is used by JSF pages.
 * @author Tamal Kanti Nath
 */
@ManagedBean
public final class Bean implements Comparator<Object> {

    private static final DatastoreService datastore =
            DatastoreServiceFactory.getDatastoreService();
    private static Map<String, String> countries = new TreeMap<>();
    private static Map<String, String> languages = new TreeMap<>();
    private static List<Package> pkg;
    private static List<Locale> locales;
    private static Map<String, String> openIds;

    private Double n = new Random().nextDouble() * -100000;
    private Date d = new Date();

    static {
        for (Locale l : Locale.getAvailableLocales()) {
            countries.put(l.getDisplayCountry(), l.getCountry());
        }
        countries.remove("");
        for (Locale l : Locale.getAvailableLocales()) {
            languages.put(l.getDisplayLanguage(), l.getLanguage());
        }
        pkg = Arrays.asList(Package.getPackages());
        Collections.sort(pkg, new Bean());
        locales = Arrays.asList(Locale.getAvailableLocales());
        Collections.sort(locales, new Bean());
        openIds = new TreeMap<>();
        openIds.put("Google", "https://www.google.com/accounts/o8/id");
        openIds.put("Yahoo", "yahoo.com");
        openIds.put("MySpace", "myspace.com");
        openIds.put("AOL", "aol.com");
        openIds.put("MyOpenId", "myopenid.com");
        if (Constants.PRODUCTION) {
            Set<String> attributes = Collections.emptySet();
            UserService userService = UserServiceFactory.getUserService();
            for (Map.Entry<String, String> e : openIds.entrySet()) {
                e.setValue(userService.createLoginURL(Constants.REDIRECT_URI,
                        null, e.getValue(), attributes));
            }
        }
    }

    /**
     * Returns a List of character sets.
     * @return character sets
     */
    @SuppressWarnings("static-method")
    public Set<String> getCharsets() {
        return Charset.availableCharsets().keySet();
    }

    /**
     * Returns a Map of country code and it's name.
     * @return countries
     */
    @SuppressWarnings("static-method")
    public Map<String, String> getCountries() {
        return countries;
    }

    /**
     * Returns a Map of language code and it's name.
     * @return languages
     */
    @SuppressWarnings("static-method")
    public Map<String, String> getLanguages() {
        return languages;
    }

    /**
     * Returns a Map of environment variables.
     * @return environment variables
     */
    @SuppressWarnings("static-method")
    public Map<String, String> getEnvironment() {
        return System.getenv();
    }

    /**
     * Returns a Map of system properties.
     * @return system properties
     */
    @SuppressWarnings("static-method")
    public Properties getProperties() {
        return System.getProperties();
    }

    /**
     * Returns a Map of API environment.
     * @return API environment
     */
    @SuppressWarnings("static-method")
    public Map<String, Object> getApiEnvironment() {
        Environment env = ApiProxy.getCurrentEnvironment();
        Map<String, Object> map = new TreeMap<>();
        map.put("appId", env.getAppId());
        map.put("authDomain", env.getAuthDomain());
        map.put("email", env.getEmail());
        map.put("moduleId", env.getModuleId());
        map.put("versionId", env.getVersionId());
        map.put("remainingMillis", env.getRemainingMillis());
        map.put("instanceId", env.getAttributes().get("com.google.appengine.instance.id"));
        map.put("default_version_hostname", env.getAttributes().get("com.google.appengine.runtime.default_version_hostname"));
        map.put("new_database_connectivity", env.getAttributes().get("com.google.appengine.runtime.new_database_connectivity"));
        map.put("request_log_id", env.getAttributes().get("com.google.appengine.runtime.request_log_id"));
        map.put("datacenter", env.getAttributes().get("com.google.apphosting.api.ApiProxy.datacenter"));
        map.put("request_id_hash", env.getAttributes().get("com.google.apphosting.api.ApiProxy.request_id_hash"));
        return map;
    }

    /**
     * Returns a Map of API environment attributes.
     * @return API environment attributes
     */
    @SuppressWarnings("static-method")
    public Map<String, Object> getSystemProperty() {
        Map<String, Object> map = new TreeMap<>();
        map.put(SystemProperty.applicationId.key(),
                SystemProperty.applicationId.get());
        map.put(SystemProperty.applicationVersion.key(),
                SystemProperty.applicationVersion.get());
        map.put(SystemProperty.environment.key(),
                SystemProperty.environment.get());
        map.put(SystemProperty.version.key(),
                SystemProperty.version.get());
        return map;
    }

    /**
     * Returns an array of packages.
     * @return packages
     */
    @SuppressWarnings("static-method")
    public List<Package> getPackages() {
        return pkg;
    }

    /**
     * Returns an array of security providers.
     * @return security providers
     */
    @SuppressWarnings("static-method")
    public Provider[] getSecurityProviders() {
        return Security.getProviders();
    }

    /**
     * Returns an array of security providers.
     * @return security providers
     */
    @SuppressWarnings("static-method")
    public String[] getTimeZone() {
        return TimeZone.getAvailableIDs();
    }

    /**
     * Returns the locale list.
     * @return the locale list
     */
    @SuppressWarnings("static-method")
    public List<Locale> getLocales() {
        return locales;
    }

    /**
     * Returns current date-time.
     * @return current date-time
     */
    public Date getNow() {
        return d;
    }

    /**
     * Returns current date-time.
     * @return current date-time
     */
    public double getNumber() {
        return n;
    }

    /**
     * Returns the supported locale list.
     * @return the supported locale list
     */
    @SuppressWarnings("static-method")
    public List<Locale> getSupportedLocales() {
        Iterator<Locale> supportedLocales = FacesContext.getCurrentInstance()
                .getApplication().getSupportedLocales();
        List<Locale> l = new ArrayList<>();
        while (supportedLocales.hasNext()) {
            l.add(supportedLocales.next());
        }
        return l;
    }

    /**
     * Returns the default locale.
     * @return the default locale
     */
    @SuppressWarnings("static-method")
    public Locale getLocale() {
        return (Locale) FacesContext.getCurrentInstance()
                .getExternalContext().getSessionMap().get(SessionKey.LOCALE);
    }

    /**
     * Returns the default locale.
     * @param locale the locale to set
     */
    @SuppressWarnings("static-method")
    public void setLocale(Locale locale) {
        Map<String, Object> session = FacesContext.getCurrentInstance()
                .getExternalContext().getSessionMap();
        if (locale == null) {
            session.remove(SessionKey.LOCALE);
        } else {
            session.put(SessionKey.LOCALE.name(), locale);
        }
    }

    /**
     * Returns a Map of currency code and it's name.
     * @return currencies
     */
    @SuppressWarnings("static-method")
    public Map<String, String> getCurrencies() {
        Map<String, String> currencies = new TreeMap<>();
        for (Currency c : Currency.getAvailableCurrencies()) {
            currencies.put(c.getDisplayName(), c.getCurrencyCode());
        }
        return currencies;
    }

    /**
     * Get Open IDs.
     * @return Open IDs
     */
    @SuppressWarnings("static-method")
    public Map<String, String> getOpenIds() {
        return openIds;
    }

    /**
     * Returns all user agents.
     * @return List of user agents
     */
    @SuppressWarnings("static-method")
    public Map<String, Object> getUserAgents() {
        Map<String, Object> userAgents = null;
        Key key = KeyFactory.createKey(Constants.USER_AGENT, Constants.CURRENT);
        Entity entity;
        try {
            entity = datastore.get(key);
        } catch (EntityNotFoundException e) {
            entity = new Entity(key);
        }
        userAgents = entity.getProperties();
        return userAgents;
    }

    /**
     * Returns Session expire time.
     * @return Session expire time
     */
    @SuppressWarnings("static-method")
    public Date getSessionExpires() {
        HttpSession session = (HttpSession) FacesContext.getCurrentInstance()
                .getExternalContext().getSession(true);
        return new Date(System.currentTimeMillis()
                + session.getMaxInactiveInterval() * 1000);
    }

    @Override
    public int compare(Object o1, Object o2) {
        return o1.toString().compareTo(o2.toString());
    }
}
